package com.tiamo.chatgpt.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.tiamo.chatgpt.common.constants.RedisCacheKeys;
import com.tiamo.chatgpt.common.constants.RedisCacheNames;
import com.tiamo.chatgpt.common.enums.ChatRoleEnum;
import com.tiamo.chatgpt.common.enums.UserLevelEnum;
import com.tiamo.chatgpt.domain.message.MessageReqDto;
import com.tiamo.chatgpt.domain.message.MessageRspDto;
import com.tiamo.chatgpt.domain.message.MsgDto;
import com.tiamo.chatgpt.domain.message.SendReqDto;
import com.tiamo.chatgpt.entity.UserEntity;
import com.tiamo.chatgpt.service.ChatKeyService;
import com.tiamo.chatgpt.service.DialogueContentService;
import com.tiamo.chatgpt.service.MessageService;
import com.tiamo.chatgpt.service.UserService;
import com.tiamo.chatgpt.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author luozhiwei
 * @date 2023/6/3 下午6:51
 */
@Slf4j
@Service
public class MessageServiceImpl implements MessageService {

    /**
     * 配置文件里面配置的对接的api-key
     */
    @Value("${chatgpt.api-key}")
    private String apiKey;

    /**
     * gpt请求路径
     */
    @Value("${chatgpt.url}")
    private String url;

    @Resource
    private RedisUtil redisUtil;
    @Resource
    private DialogueContentService dialogueContentService;
    @Resource
    private ChatKeyService chatKeyService;
    @Resource
    private UserService userService;

    /**
     * 发送
     *
     * @param reqDto
     * @return
     */
    @Override
    public List<MsgDto> send(SendReqDto reqDto) {
        //将提问放到本地缓存，拼接成上下文。这个地方会把所有的问题与回答与新提的问题都拼接成一个消息内容的list
        List<MsgDto> msgDtos = addCache(reqDto.getUserId(), reqDto.getDialogueId(), ChatRoleEnum.USER.getCode(), reqDto.getMessage());

        //todo 查看的官方的解决方案，若还不能解决上下文问题，选择适当的放大范围试试。暂时还没有找到更好的处理方案。也别发太大，官方接口有限制请求数据大小
        MessageReqDto dto = MessageReqDto.builder().messages(msgDtos).build();

        //这个用的是hutool的工具包
        HttpRequest request = HttpUtil.createPost(url);
        //设置请求头
        request.addHeaders(getHeaderMap(reqDto.getUserId()));
        //设置请求参数
        request.body(JSONUtil.toJsonStr(dto));
        HttpResponse execute = request.execute();

        //处理消息返回转换成指定格式
        MessageRspDto messageRspDto = formatRsp(execute.body());
        //将gpt返回信息拼接到消息记录里面，组成消息上下文，避免出现问答不一
        msgDtos = addCache(reqDto.getUserId(), reqDto.getDialogueId(), messageRspDto.getRole(), messageRspDto.getContent());

        //截取返回数据最后两条存入数据库
        List<MsgDto> inDbList = msgDtos.subList(msgDtos.size() - 2, msgDtos.size());
        dialogueContentService.insertNewContent(reqDto.getUserId(), reqDto.getDialogueId(), inDbList);
        return msgDtos;
    }

    /**
     * 处理消息内容上下文存储缓存
     *
     * @param userId
     * @param role
     * @param message
     * @param dialogueId
     * @return
     */
    private List<MsgDto> addCache(Long userId, Long dialogueId, String role, String message) {
        String key = RedisCacheNames.DIALOGUE + RedisCacheKeys.USER_DIALOGUE_CONTENT_LAST + userId + "_" + dialogueId;
        List<MsgDto> msgDtos = redisUtil.listGetAllAndConvert(key, MsgDto.class);
        MsgDto dto = MsgDto.builder().role(role).content(message).build();
        if (CollUtil.isEmpty(msgDtos)) {
            msgDtos = CollUtil.newArrayList(dto);
        } else {
            msgDtos.add(dto);
        }
        if (msgDtos.size() > 4) {
            msgDtos.remove(0);
            redisUtil.listLeftDel(key);
        }
        redisUtil.listSet(key, dto);
        return msgDtos;
    }

    /**
     * 处理请求头
     *
     * @return
     */
    private Map<String, String> getHeaderMap(Long userId) {
        Map<String, String> headerMap = new HashMap<String, String>() {{
            put("Content-Type", "application/json");
            put("Authorization", "Bearer " + chatKeyService.next());
        }};
        UserEntity userEntity = userService.getById(userId);
        if (Objects.nonNull(userEntity) && Objects.nonNull(userEntity.getLevel())
                && UserLevelEnum.ONLY.getCode().equals(userEntity.getLevel()) && StringUtils.isNotBlank(userEntity.getApiKey())) {
            headerMap.replace("Authorization", "Bearer " + userEntity.getApiKey());
        }
        return headerMap;
    }

    private MessageRspDto formatRsp(String body) {
        log.warn("gtp返回数据:{}", JSON.toJSONString(body));
        JSONObject entries = JSONUtil.parseObj(body);
        JSONArray choices = entries.getJSONArray("choices");
        //choices是一个集合，会有多个，但是正常情况下大概率就只有一个。所以直接取的第一个
        JSONObject o = (JSONObject) choices.get(0);
        //转换成指定的所需要的数据格式
        MessageRspDto message = o.get("message", MessageRspDto.class);
        return message;
    }
}
